﻿using System;
using System.IO;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data.Import.OBJLoader;
using RayDen.Library.Entity;
using RayDen.Library.Entity.Frames;
using RayDen.Library.Entity.Interface;
using RayDen.Library.Entity.Renderer;
using RayDen.Library.Entity.SceneGraph;
using RayDen.MegaRay.GPU;
using RayDen.RayEngine;
using RayDen.RayEngine.Core.Devices;
using RayDen.RayEngine.Engines.PathTracer.Volume;
using RayDen.RayEngine.IntersectionEngines;
using RayDen.MegaRay.Imaging;
using RayDen.RayEngine.Scene;

namespace RayDen.MegaRay
{
    using ImageDevice = BaseFilm<RgbSpectrum>;

    public class EngineManager : IDisposable
    {
        protected const bool AreaLightAsMeshLight = true;

        public IRendererOutputSettings OutputSettings { get; set; }
        public IRendererQualitySettings QualitySettings { get; set; }


        protected ConfigurationManager Configuration;
        protected CacheManager Cache;

        protected EngineContext Context;

        protected MegaRay.GPU.MegarayIntersectionDevice IntersectionDevice;
        protected ImageDevice        ImagingDevice;

        protected RayEngineScene Scene;
        protected string FrameName;


        protected SceneBuilder _builder;

        public EngineManager()
        {
            Configuration = new ConfigurationManager();
            Cache = new CacheManager();
            
        }

        public void Initialize()
        {
            if (this.Scene == null)
            {
                throw new ArgumentException("Scene is not initialized.");
            }

            this.IntersectionDevice =
                //new OpenClIntersectionDevice(scene, true, 0);
                new MegarayIntersectionDevice(Scene, null);
            //this.ImagingDevice = new ImageDevice(this.En)

        }


        public void LoadFrameDescription(FrameDescription frame)
        {
            this.Configuration.Merge(frame);
            this.OutputSettings = new OutputSettingsInfo(frame);
            this.QualitySettings = new QualitySettingsInfo(frame);

            this.FrameName = frame.FrameName;
            var width = frame.Get<int>("ImageWidth");
            var height = frame.Get<int>("ImageHeight");

            Directory.SetCurrentDirectory(frame.WorkingDir);
            /*
            foreach (var frameElement in frame.Elements.Where(frameElement => frameParsers.ContainsKey(frameElement.GetType()))) {
                frameParsers[frameElement.GetType()](frameElement);
            }*/

            if (Scene == null)
            {
                SceneGraphService svc = new SceneGraphService();
                svc.Populate(new GlobalIndexObjLoader(), new MaterialLoader());

                //svc.OpenFrame(frame);
                _builder = new SceneBuilder() { SGService = svc };
                _builder.Setup();
                this.Scene = _builder.OpenFrame(frame, AreaLightAsMeshLight);
                this.Scene.DefaultLightGain = Configuration.Get("LightGain", new RgbSpectrum(1.0f));
                this.Scene.MaxPathDepth = Configuration.Get("MaxPathDepth", 5);
                this.Scene.RussianRuletteImportanceCap = Configuration.Get("RRImportanceCap", 0.75f);
                this.Scene.MaxPaths = Configuration.Get("PathBufferSize", 65536 >> 10);
                this.Scene.ShadowRayCount = Configuration.Get("ShadowRayCount", 1);
                this.Scene.TextureSamplingQuality = this.QualitySettings.TextureSamplingQuality;
                this.Scene.LightSamplingStrategy = LightSamplingStrategy.UniformOneLight;

                var scene = _builder.SceneGeo;

                var sceneBound = new AABB(scene.BoundingSphereCenter - scene.BoundingSphereRadius,
                                          scene.BoundingSphereCenter + scene.BoundingSphereRadius);
                sceneBound.Expand(1f);

                //var viewBound = new AABB(Scene.Camera.Target - 5f, Scene.Camera.Target + 5f);
                var c = new Point(Scene.Camera.Target);
                var bb = new AABB(c - 1f, c + 1f);
                var volumeBound = sceneBound;
                Scene.VolumeIntegrator = new SingleScatteringIntegrator(volumeBound,
                                                                         volumeBound.Size.Length / 10f, //step
                                                                         0.005f, //Probability
                                                                         RgbSpectrum.UnitSpectrum() * 0.095f,  //Inscatering
                                                                         new RgbSpectrum(0.00005f)             //Emission
                                                                       );
            }

            this.Context = new EngineContext() { RenderThreads = Configuration.Get("RenderThreadsCount", 1), FrameConfiguration = frame, Scene = this.Scene };
            this.Initialize();
        }



        public void Dispose()
        {
            if (this.IntersectionDevice != null)
            {
                this.IntersectionDevice.Stop();
                this.IntersectionDevice.Dispose();
                this.IntersectionDevice = null;
            }
        }
    }
}